-
Notifications
You must be signed in to change notification settings - Fork 435
Add URL Routing Support for MCP HTTP Transport #622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add URL Routing Support for MCP HTTP Transport #622
Conversation
Implements URL-based tool filtering to enable context-specific tool collections for multi-agent systems and specialized agent workflows. Features: - McpServerToolRouteAttribute for method-level route assignment - WithHttpTransportAndRouting() extension for route-aware transport - MapMcpWithRouting() for automatic route discovery and endpoint mapping - RouteAwareToolService for path normalization and route management - Session-based tool filtering preserving resources and prompts Key capabilities: - Global tools (no route attribute) available on all routes - Route-specific tools only accessible on designated endpoints - Multi-route tools available on multiple specified routes - Backward compatibility with existing WithHttpTransport/MapMcp Example usage: [McpServerToolRoute("admin")] // Available at /mcp/admin [McpServerToolRoute("weather", "utilities")] // Multi-route tool Use cases: - Multi-agent system coordination with specialized tool sets - Agent workflow orchestration with phase-appropriate capabilities - Context-aware tool separation for different agent types - Secure agent collaboration through filtered tool access Tests: Comprehensive test suite covering routing logic, edge cases, and backward compatibility scenarios
@@ -64,6 +64,38 @@ public sealed class McpServerToolCreateOptions | |||
/// </remarks> | |||
public string? Title { get; set; } | |||
|
|||
private IReadOnlyList<string>? routes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe better use as property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can do, switched it to a property.
<ItemGroup> | ||
<Folder Include="Attributes\" /> | ||
</ItemGroup> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the folder.
-Removed Attributes folder -Changed routes getter and setter to a Routes property Change also made for consistency: -Removed Services folder and namespace -Moved RouteAwareToolService.cs out of the folder and namespace -Removed reference to this namespace from McpRouteAwarenessExtension.cs
…n/csharp-sdk into feature/url-routing-sse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll probably end up closing this in favor of a smaller change that adds something like a MapMcp(string pattern, Action<McpServerOptions>)
overload, but I'm curious to hear what others think.
/// For example, a route name "echo" will be accessible at /mcp/echo when the global | ||
/// route is configured as "/mcp". | ||
/// </remarks> | ||
public IReadOnlyList<string> Routes { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephentoub @eiriktsarpalis @PederHP Do you have any thoughts on filtering which tools are available on which routes via an attribute?
I think having different routes for different sets of tools is an important scenario, but I don't know that attributes are the best way to achieve this.
Currently, you can already specify different tools for different routes using ConfigureSessionOptions
which this PR uses to implement the functionality of this attribute under the covers, but that's not the most convenient thing if you're using attributes WithToolsFromAssembly.
I think a middle ground might be to add an MapMcp(string pattern, Action<McpServerOptions>)
overload.
MapMcp(string pattern, Action<IMcpServerBuilder>)
would probably be even more convenient, but that won't work when MapMcp is called, because the service provider has already been built at that point.
/// </para> | ||
/// </remarks> | ||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="endpoints"/> is null.</exception> | ||
public static IEndpointConventionBuilder MapMcpWithRouting(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern = "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should add a new MapMcpWithRouting
method. If we want to support routing via attributes, we should support that via the normal MapMcp
method.
// Map MCP endpoints for all discovered routes | ||
foreach (var discoveredRoute in routeService.OtherRoutes) | ||
{ | ||
endpoints.MapMcp(discoveredRoute); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should be adding any routes that are not specified via the pattern
argument. If we use attributes, it should only be used to filter which tools are available on routes that were already registered explicitly.
That's all fine with me depending on what you decide. I wanted to throw an idea out there that felt like typical Microsoft magic, but if the maintainers decide on a specific implementation to this I would definitely love to take on the responsibility of putting it together if that is possible. Working on this was honestly a great experience :). |
I built a tool filtering extension to handle this. So it's multiple implementations of the same MCP in the config, with a different header for each set of tools. Been meaning to extend it to resources and prompts also, so that'll be next. |
Adds URL routing support for MCP HTTP transport, enabling route-aware tool filtering where different tool sets can be exposed at different HTTP endpoints using the new
[McpServerToolRoute]
attribute.Motivation and Context
Enables context-specific tool collections for multi-agent systems and organizational security boundaries. Currently, MCP servers expose all registered tools at a single HTTP endpoint, which limits flexibility in scenarios where different clients or contexts require access to different tool sets.
Our company's multi-agent system requires different tool sets for different agent types (research agents need web search tools, execution agents need file operation tools). This feature enables route-based tool filtering while maintaining backward compatibility.
Currently, MCP servers expose all registered tools at a single HTTP endpoint, forcing every client to see every tool regardless of context. This creates several problems:
This feature enables route-based tool filtering (e.g.,
/mcp/admin
,/mcp/utilities
) while maintaining backward compatibility, addressing community requests for selective tool exposure and better multi-agent system support.How Has This Been Tested?
Comprehensive unit tests in
MapMcpRoutingTests.cs
covering:MapMcp()
Complete sample application
UrlRoutingSseServer
demonstrating:admin
,weather
,math
,echo
,utilities
)Manual testing with curl verifying:
/mcp
returns all 9 tools (global route)/mcp/admin
returns 3 tools (admin + global tools only)/mcp/weather
returns 3 tools (weather + global tools only)/mcp/math
returns 3 tools (math + global tools only)Backward compatibility verification:
WithHttpTransport()
andMapMcp()
methods unchangedBreaking Changes
None - fully backward compatible. All existing tests pass without modification. New functionality is opt-in via
WithHttpTransportAndRouting()
andMapMcpWithRouting()
.Types of changes
Checklist
Additional context
Implementation Architecture
The routing system works through session-level tool filtering rather than endpoint-level routing:
MapMcpWithRouting()
scans all registered tools for[McpServerToolRoute]
attributes and creates separate HTTP endpoints for each discovered route/mcp/admin
), theConfigureSessionOptions
callback filters the available tools before the session startsDesign Decision: Session-Level vs. Endpoint-Level Filtering
This implementation filters tools per-session rather than creating separate MCP server instances per route. Alternative approaches considered:
tools/list
responses) in middleware components before they're sent to clients, and rejecting unauthorizedtools/call
requests. This adds complexity to the request pipeline and requires handling JSON-RPC message parsing/modification.Session-level filtering provides the cleanest separation while maintaining full backward compatibility and leveraging existing MCP session infrastructure.
Route Normalization
All route paths undergo consistent normalization (leading slash, trailing slash removal, duplicate slash collapsing) to ensure reliable matching regardless of how routes are specified in code vs. HTTP requests.
Tool Visibility Model
The implementation follows a inheritance-based tool visibility model:
/mcp
): Exposes ALL registered tools regardless of route attributes/mcp/admin
,/mcp/weather
): Expose only their route-specific tools PLUS all global tools[McpServerToolRoute]
attributes appear on every route, providing common functionality across all contextsExample with 9 total tools:
/mcp
→ 9 tools (2 admin + 2 weather + 2 math + 2 echo + 1 global)/mcp/admin
→ 3 tools (2 admin + 1 global)/mcp/weather
→ 3 tools (2 weather + 1 global)This example is if the base route is set to "mcp".
This model ensures that essential tools (logging, diagnostics, etc.) remain available across all contexts while still enabling route-specific specialization.